/* ***** BEGIN LICENSE BLOCK *****
 * The MIT License
 *
 * Copyright (c) 2009 Stan Angeloff
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 * ***** END LICENSE BLOCK ***** */

if (typeof (extensions) === 'undefined')
	window.extensions = {};

(function() {

	const Cc = Components.classes;
	const Ci = Components.interfaces;
	const Cr = Components.results;

	const XUL_NS = 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul';

	var osService, osPathService, fastOpenService, partService, consoleService,
		osDirectorySeparator;

	consoleService = Cc['@mozilla.org/consoleservice;1'].getService(Ci.nsIConsoleService);

	var $self = extensions.projectsFastOpen || (extensions.projectsFastOpen = {}),

		$l10n = Cc['@mozilla.org/intl/stringbundle;1'].getService(Ci.nsIStringBundleService)
													  .createBundle('chrome://projectsfastopen/locale/projectsfastopen.properties'),

		$log = function(message) { consoleService.logStringMessage(message); };

	function ProjectsFastOpenUIDriver() {};

	ProjectsFastOpenUIDriver.prototype.constructor = ProjectsFastOpenUIDriver;

	ProjectsFastOpenUIDriver.prototype.QueryInterface = function(iid) {

		if ( ! iid.equals(Ci.koIFastOpenUIDriver) &&
			 ! iid.equals(Ci.nsISupports))
			throw Cr.NS_ERROR_NO_INTERFACE;

		return this;
	};

	ProjectsFastOpenUIDriver.prototype.setTreeView = function(view) {

		$self._resultsEl.treeBoxObject.view = view;
	};

	ProjectsFastOpenUIDriver.prototype.setCurrPath = function(path) {

		$self._messageEl.setAttribute('value', path);
	};

	ProjectsFastOpenUIDriver.prototype.searchStarted = function() {

		$self._loadingEl.setAttribute('collapsed', 'false');
	};

	ProjectsFastOpenUIDriver.prototype.searchAborted = function() {

		$self._loadingEl.setAttribute('collapsed', 'true');
	};

	ProjectsFastOpenUIDriver.prototype.searchCompleted = function() {

		$self._loadingEl.setAttribute('collapsed', 'true');
	};

	$self._queryEl = null;
	$self._projectTreeEl = null;
	$self._resultsEl = null;
	$self._messageEl = null;
	$self._loadingEl = null;
	$self._uiDriver = null;
	$self._session = null;
	$self._viewsOpening = 0;

	$self._ignoreNextFindFiles = false;

	$self.setDefaultKeyBindings = function() {

		var commandName = 'cmd_projectsFastOpen_togglePane';

		// Make sure the User has not overridden the default key bindings
		var existingKeyBindings = gKeybindingMgr.command2keysequences(commandName);
		if (existingKeyBindings.length < 1) {

			var defaultKeyBindings = {};

			defaultKeyBindings[commandName] = ['Ctrl+`'];
			gKeybindingMgr._add_keybinding_sequences(defaultKeyBindings);
		}
	};

	$self._extendSelectTreeRow = function(tree, index) {

		tree.view.selection.rangedSelect(index, index, true);
	    tree.treeBoxObject.ensureRowIsVisible(index);
	};

	$self._selectTreeRow = function(tree, index) {

		tree.view.selection.select(index);
		tree.treeBoxObject.ensureRowIsVisible(index);
	};

	$self._completeSelectionOrMove = function(moveForward, allowAdvance) {

		var hits = $self._resultsEl.view.getSelectedHits(new Object());

		if (hits.length !== 1)
			return null;

		var hit = hits[0];

		// Determine the new value to which to complete
		var currentValue = $self._queryEl.value,
			newValue = osPathService.join(osPathService.dirname($self._queryEl.value), hit.base);

		if (hit.isdir)
			// Selecting a dir should just enter that dir into the filter box
			newValue += osDirectorySeparator;

		// If the new value we'd complete to is already the selected value then advance to the next result
		if (allowAdvance && newValue == currentValue) {

			var index;

			if (moveForward) {

				index = $self._resultsEl.currentIndex + 1;

				if (index < 0)
					index = 0;

				if (index < $self._resultsEl.view.rowCount)
					$self._selectTreeRow($self._resultsEl, index);

			} else {

				index = $self._resultsEl.currentIndex - 1;

				if (index >= 0)
					$self._selectTreeRow($self._resultsEl, index);
			}

			// `allowAdvance=false` to not jump over multiple entries with the
			// same basename (e.g. all the 'Conscript' files in Komodo source tree)
			return $self._completeSelectionOrMove(moveForward, false);
		}

		// Set the new value. If it is a dir, then descend into it
		$self._queryEl.value = newValue;
		if (hit.isdir)
			$self.handleQueryUpdate();

		return null;
	};

	$self.handleQueryKey = function(event) {

		var isResultsTreeVisible = $self._isResultsTreeVisible(),
			index;

		if (event.keyCode == KeyEvent.DOM_VK_ENTER || event.keyCode == KeyEvent.DOM_VK_RETURN) {

			if (isResultsTreeVisible) {

				// Can't turn off <Enter> firing oncommand on the <textbox type="timed">,
				// therefore tell the handler to ignore the coming one.
				$self._ignoreNextFindFiles = true;

				$self.openSelected();

				event.preventDefault();
				event.stopPropagation();

			} else
				$self._projectTreeEl.focus();

		} else if (event.keyCode == KeyEvent.DOM_VK_UP && event.shiftKey) {

			var treeEl = (isResultsTreeVisible ? $self._resultsEl : $self._projectTreeEl);

			index = treeEl.currentIndex - 1;

			if (index >= 0)
				$self._extendSelectTreeRow(treeEl, index);

			event.preventDefault();
			event.stopPropagation();

		} else if (event.keyCode == KeyEvent.DOM_VK_DOWN && event.shiftKey) {

			var treeEl = (isResultsTreeVisible ? $self._resultsEl : $self._projectTreeEl);

			index = treeEl.currentIndex + 1;

			if (index < 0)
				index = 0;

			if (index < treeEl.view.rowCount)
				$self._extendSelectTreeRow(treeEl, index);

			event.preventDefault();
			event.stopPropagation();

		} else if (event.keyCode == KeyEvent.DOM_VK_TAB && event.shiftKey) {

			if (isResultsTreeVisible) {

				$self._completeSelectionOrMove(false, true);

				event.preventDefault();
				event.stopPropagation();
			}

		} else if (event.keyCode == KeyEvent.DOM_VK_TAB) {

			if (isResultsTreeVisible)
				$self._completeSelectionOrMove(true, true);
			else
				$self._projectTreeEl.focus();

			event.preventDefault();
			event.stopPropagation();

		} else if (event.keyCode == KeyEvent.DOM_VK_UP || (event.ctrlKey && event.charCode === 112)) {

			var treeEl = (isResultsTreeVisible ? $self._resultsEl : $self._projectTreeEl);

			index = treeEl.currentIndex - 1;

			if (index >= 0)
				$self._selectTreeRow(treeEl, index);

			event.preventDefault();
			event.stopPropagation();

		} else if (event.keyCode == KeyEvent.DOM_VK_DOWN || (event.ctrlKey && event.charCode === 110)) {

			var treeEl = (isResultsTreeVisible ? $self._resultsEl : $self._projectTreeEl);

			index = treeEl.currentIndex + 1;
			if (index < 0)
				index = 0;

			if (index < treeEl.view.rowCount)
				$self._selectTreeRow(treeEl, index);

			event.preventDefault();
			event.stopPropagation();

		} else if (event.keyCode == KeyEvent.DOM_VK_ESCAPE) {

			$self._queryEl.value = '';
			$self.handleQueryUpdate();

			event.preventDefault();
			event.stopPropagation();
		}
	};

	$self.openSelected = function() {

		var hits = $self._resultsEl.view.getSelectedHits(new Object()),
			hit, viewType, tabGroup, uri,
			closePane = false;

		for (var i in hits) {

			var hit = hits[i];
			if (hit.isdir) {

				// Selecting a dir should just enter that dir into the filter box
				$self._queryEl.value = osPathService.join(osPathService.dirname($self._queryEl.value), hit.base) + osDirectorySeparator;
				$self.handleQueryUpdate();

			} else if (hit.type == 'open-view') {

				hit.view.makeCurrent();
				closePane = true;

			} else {

				viewType = 'editor';
				tabGroup = null;
				uri = ko.uriparse.pathToURI(hit.path);

				$self._viewsOpening ++;
				ko.views.manager.openViewAsync(viewType, uri, tabGroup, null, $self._viewOpened);

				closePane = true;
			}
		}

		if (closePane) {

			$self.abortSession();
			$self.togglePane(null);

			$self._queryEl.value = '';
		}
	};

	$self._viewOpened = function() {

		$self._viewsOpening --;
		if ($self._viewsOpening < 1) {

			window.setTimeout(function() {

				// If the pane is visible, put focus back in query after all views have opened
				var splitterWidgetEl = document.getElementById('workspace_left_splitter');
				if (splitterWidgetEl.getAttribute('collapsed') != 'true') {

					$self._queryEl.focus();
					$self._queryEl.select();
				}

			}, 1);
		}
	};

	$self._showProjectsTree = function() {

		$self._resultsEl.setAttribute('collapsed', 'true');
		$self._projectTreeEl.setAttribute('collapsed', 'false');

		$self._messageEl.setAttribute('value', '');
	};

	$self._showResultsTree = function() {

		$self._projectTreeEl.setAttribute('collapsed', 'true');
		$self._resultsEl.setAttribute('collapsed', 'false');
	};

	$self._isResultsTreeVisible = function() {

		return ($self._resultsEl.getAttribute('collapsed') == 'false');
	};

	$self.handleQueryUpdate = function() {

		if ($self._ignoreNextFindFiles)
			$self._ignoreNextFindFiles = false;
		else {

			var value = $self._queryEl.value;

			if (value.length) {

				$self._showResultsTree();

				$self._session.findFiles(value);

			} else
				$self._showProjectsTree();
		}
	};

	$self.setUpQueryElement = function() {

		var filterEl = document.getElementById('partviewer-filter-textbox');

		// Hide the filter textbox from Komodo
		filterEl.setAttribute('collapsed', 'true');

		// Replace with our own textbox
		$self._queryEl = document.createElementNS(XUL_NS, 'textbox');

		$self._queryEl.setAttribute('id', 'projectsfastopen-query');

		$self._queryEl.setAttribute('class', filterEl.getAttribute('class') || 'search-box');
		$self._queryEl.setAttribute('flex', filterEl.getAttribute('flex') || '1');
		$self._queryEl.setAttribute('type', 'timed');
		$self._queryEl.setAttribute('timeout', '200');
		$self._queryEl.setAttribute('tooltiptext', $l10n.GetStringFromName('togglePane.tooltipText'));

		$self._queryEl.addEventListener('focus', function() { window.updateCommands('undo'); }, null);
		$self._queryEl.addEventListener('blur', function() { window.updateCommands('undo'); }, null);

		$self._queryEl.addEventListener('command', $self.handleQueryUpdate, null);
		$self._queryEl.addEventListener('keypress', $self.handleQueryKey, null);

		filterEl.parentNode.appendChild($self._queryEl);
	};

	$self.setUpResultsElement = function() {

		$self._projectTreeEl = document.getElementById('project-tree');

		$self._resultsEl = document.createElementNS(XUL_NS, 'tree');

		$self._resultsEl.setAttribute('id', 'projectsfastopen-results');
		$self._resultsEl.setAttribute('flex', '1');
		$self._resultsEl.setAttribute('hidecolumnpicker', 'true');
		$self._resultsEl.setAttribute('collapsed', 'true');

		$self._resultsEl.addEventListener('select', function() { $self._resultsEl.view.selectionChanged(); }, null);
		$self._resultsEl.addEventListener('dblclick', $self.openSelected, null);

		var treeColumnsEl = document.createElementNS(XUL_NS, 'treecols'),
			treeDefaultColumnEl = document.createElementNS(XUL_NS, 'treecol'),
			treeChildrenEl = document.createElementNS(XUL_NS, 'treechildren');

		treeDefaultColumnEl.setAttribute('id', 'projectsfastopen-results-file');
		treeDefaultColumnEl.setAttribute('hideheader', 'true');
		treeDefaultColumnEl.setAttribute('flex', '1');

		treeColumnsEl.appendChild(treeDefaultColumnEl);
		$self._resultsEl.appendChild(treeColumnsEl);
		$self._resultsEl.appendChild(treeChildrenEl);
		$self._projectTreeEl.parentNode.appendChild($self._resultsEl);
	};

	$self.togglePane = function(event) {

		// Toggle the left pane -- code taken from ko.uilayout.togglePane
		var splitterWidgetEl = document.getElementById('workspace_left_splitter'),
			tabsEl = document.getElementById('project_toolbox_tabs');

		if (splitterWidgetEl.getAttribute('collapsed') == 'true') {

			$self.restartSession();

			if ($self._queryEl.value.length)
				$self._showResultsTree();

			var scimoz = null;

			if (ko.views.manager.currentView &&
				ko.views.manager.currentView.scimoz) {

				scimoz = ko.views.manager.currentView.scimoz;

				var firstVisibleLine = scimoz.firstVisibleLine,
					firstActualLine = scimoz.docLineFromVisible(firstVisibleLine),
					lastActualLine = scimoz.docLineFromVisible(firstVisibleLine + scimoz.linesOnScreen),
					currentLine = scimoz.lineFromPosition(scimoz.currentPos);

				if (currentLine < firstVisibleLine ||
					currentLine > lastActualLine)
					scimoz = null;

				ko.uilayout.toggleSplitter('cmd_viewLeftPane');

				if (scimoz)
					window.setTimeout(function() { scimoz.scrollCaret(); }, 100);

			} else
				ko.uilayout.toggleSplitter('cmd_viewLeftPane');

			$self._queryEl.focus();
			$self._queryEl.select();

		} else {

			$self._showProjectsTree();

			if (xtk.domutils.elementInFocus(tabsEl.parentNode)) {

				if (ko.views.manager.currentView)
					ko.views.manager.currentView.setFocus();
				else
					window.focus();
			}

			ko.uilayout.toggleSplitter('cmd_viewLeftPane');
		}

		if (event) {

			// To be safe, prevent any default behaviour on this key
			event.preventDefault();
			event.stopPropagation();
		}
	};

	$self.initializeSession = function() {

        osService = Cc['@activestate.com/koOs;1'].getService(Ci.koIOs);
        osPathService = Cc['@activestate.com/koOsPath;1'].getService(Ci.koIOsPath);
        fastOpenService = Cc['@activestate.com/koFastOpenService;1'].getService(Ci.koIFastOpenService);
        partService = Cc['@activestate.com/koPartService;1'].getService(Ci.koIPartService);

        osDirectorySeparator = osService.sep;

		$self._uiDriver = new ProjectsFastOpenUIDriver();
        $self._session = fastOpenService.getSession($self._uiDriver);
	};

	$self.restartSession = function() {

		$self._session.setCurrProject(partService.currentProject);

		var historyViews = [];

		if (ko.views.manager.topView &&
			ko.views.manager.topView.viewhistory) {

			var recentViews = ko.views.manager.topView.viewhistory.genRecentViews();

			for (view in recentViews)
				historyViews.push(view);
		}

		$self._session.setOpenViews(historyViews.length, historyViews);
        $self._session.setCurrHistorySession(ko.history.curr_session_name());
	};

	$self.abortSession = function() {

		$self._session.abortSearch();
	};

	$self.initialize = function() {

		$self._messageEl = document.getElementById('statusbar-message');
		$self._loadingEl = document.getElementById('statusbar-loading');

		// We have registered a broadcaster and we need to apply some default keybindings to it
		$self.setDefaultKeyBindings();

		$self.setUpQueryElement();
		$self.setUpResultsElement();

		$self.initializeSession();
	};

	window.addEventListener('load', $self.initialize, null);
	window.addEventListener('unload', $self.abortSession, null);

	window.addEventListener('current_project_changed', $self.restartSession, true);
	window.addEventListener('project_added', $self.restartSession, true);
	window.addEventListener('project_removed', $self.restartSession, true);

	(function() {
		for (var key in $self)
			if (typeof ($self[key]) === 'function')
				(function(fn) {

					$self[key] = function() {

						try { return fn.apply($self, arguments || []); }
						catch (e) {

							ko.dialogs.alert($l10n.GetStringFromName('uncaughtException'),
											 $self.print_r(e));

							throw e;
						}

						return null;
					};

				})($self[key]);
	})();

	// Code copied from http://www.openjs.com/scripts/others/dump_function_php_print_r.php
	//   Thanks A Sohn!

	const MAX_DEPTH = 10;

	$self.str_repeat = function(str, repeat) {

		var output = '';
		for (var i = 0; i < repeat; i ++)
			output += str;

		return output;
	};

	$self.print_r = function(obj, indent, depth) {

		var ws = '    ',
			output = '';

		indent = indent || 0;
		depth = depth || 0;

		if (depth > MAX_DEPTH)
			return $self.str_repeat(ws, indent) + '*Maximum depth reached*\n';

		if (typeof (obj) === 'object') {

			output += (indent == 0) ? typeof (obj) + '\n(\n' : '';
			indent ++;

			var child = '';
			for (var key in obj) {

				try { child = obj[key]; }
				catch (e) { child = '*Unable to evaluate*'; }

				output += $self.str_repeat(ws, indent) + '[' + key + '] => ';

				if (typeof (child) === 'object') {

					indent ++;

					output += typeof (child) + '\n';
					output += $self.str_repeat(ws, indent) + '(\n';
					output += $self.print_r(child, indent, depth+1);
					output += $self.str_repeat(ws, indent) + ')\n';

					indent --;

				} else
					output += child + '\n';

			}

			indent --;

			output += (indent == 0) ? ')\n' : '';

			return output;

		} else
			return $self.str_repeat(ws, indent) + obj + '\n';
	};

})();
